package cn.autumnorange.app.common.provider.rpc.lock.strategy;

import cn.autumnorange.app.common.dto.ResultDto;
import cn.autumnorange.app.common.provider.rpc.ProviderRPCService;
import cn.autumnorange.app.common.rpc.anntation.AbstractAnnotationStrategy;
import cn.autumnorange.app.common.rpc.lock.RPCIdempotentLockTarget;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.http.ResponseEntity;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/** 前端传入rpcid header头作为锁,没有则校验控制层注解是否有锁注解并取对应对象id,对于聚合对象循环遍历处理找到对应指定的类名或者表名+id对象 如xxx.xxx.User#id */
@Slf4j
public class RpcidAnnotationnStrategy extends AbstractAnnotationStrategy {
  public static String ANNOTATIONSTRATEGY = RPCIdempotentLockTarget.class.getName();
  private  ProviderRPCService providerRPCService;
  public static final String REDISLOCKPREFIX = "providerLock#";

  public RpcidAnnotationnStrategy() {};

  public RpcidAnnotationnStrategy(
      RedissonClient redissonClient,
      ProviderRPCService providerRPCService,
      long connectTimeout,
      long readTimeout) {
    this.redissonClient = redissonClient;
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
    this.providerRPCService = providerRPCService;

    this.setNeedAnnotation(false);
  }

  private RedissonClient redissonClient;

  private long connectTimeout;

  private long readTimeout;

  //  //   重试负载均衡其他节点的最大重试次数，不包括首次server
  //  private int maxAutoRetries;
  //  //     负载到其他服务器的请求总次数
  //  private int maxAutoRetriesNextServer;

  @Override
  public Object annotationTargetHandle(MethodInvocation invocation, Annotation annotation) throws Throwable {
    Exception exception=null;
    Object result = null;
    String rpcId = providerRPCService.getRPCIDByHeader();
    // 锁时间为消费端调用生产端的总时间
    //    String lockTime=consumerRPCService.getRPCLockTimeToHeader();
    // 前端未分配锁id 处理后端是否注解锁id
    //    Object[] args = point.getArgs(); // 获取目标方法的所有参数的值
    if (StringUtils.isNotBlank(rpcId)) {
      rpcId=REDISLOCKPREFIX+rpcId;
      long rpcTimeOut =
        connectTimeout + readTimeout;
      // 加锁是否成功
      boolean isLock = false;
      RLock lock = null;
      try {
        lock = redissonClient.getLock(rpcId);
        // 可以做一些前置处理
        //      对该次请求流程加锁实现幂等
        //      redisLockRegistry.obtain(ricId).tryLock();
        //      加锁结果
        //      log.info("切面介入工作....前置通知 获得header ricId:" + ricId + " ,rpcTimeOut:" + rpcTimeOut);
        if (lock != null) {
            log.info("rpcId:" + rpcId + " ,rpcTimeOut:" + rpcTimeOut);
          // 加看门狗lock无限续锁
            isLock = lock.tryLockAndStartWatchDog(0l, rpcTimeOut, TimeUnit.MILLISECONDS);
            log.info("生产端线程:"+Thread.currentThread().getId()+" "+Thread.currentThread().getName()+" 锁: "+rpcId + " 加锁结果:" + isLock );

        }
        if (!isLock) {
          //          返回500加锁异常信息给消费端
          //          throw new RuntimeException(
          //                  ProviderRPCService.RPCID
          //                          + ":"
          //                          + rpcId
          //                          + " 被其他服务锁住还没执行完毕无法释放,"
          //                          + "本服务不能执行,在没锁的情况下"
          //                          + "如果本服务正常执行完不报错调用方的事务不会回滚,"
          //                          + "上个服务也正常执行完了没报错调用方的事务也不会回滚,"
          //                          + "那么该接口相当于重复执行俩次造成数据错误,非幂等"
          //                          + "服务不可执行重复异常!!!!!!!");
          ResultDto resultDto =
              ResultDto.createResult(500, null, "无法加锁", null, null, "生产端" + rpcId + "加锁失败");
          result = ResponseEntity.ok(resultDto);

        }
        //        if (isLock) {
        //          throw new RuntimeException("provider测试异常返回");
        //        }
        // 拿到注解的值
        //        MethodSignature signature = (MethodSignature) point.getSignature();
        //        Method method = signature.getMethod();
        //        RPCIdempotentLockTarget annotation =
        // method.getAnnotation(RPCIdempotentLockTarget.class);

        // 根据你的需求获取标注在注解的值
        //      String key = annotation.key();
        //      int lockTime = annotation.lockTime();
        //      log.info("切面介入工作....前置通知 获取lockTime:" + lockTime);

        //        if (args != null && args.length > 0) {
        //          // 根据你的需求获取被切方法的args参数
        //        }
        else {
          // 加锁成功执行业务代码
          result = invocation.proceed();
        }

      } catch (Exception e) {
        //      log.info("切面介入工作....异常通知");
        exception= e;
      } finally {
        // 做一些善后工作
        // finally语句有一个执行细节，如果在try或者catch语句内有return语句，
        // 则return语句在finally语句执行结束后才执行，但finally并不能改变返回值
        //      log.info("切面介入工作....后置通知:本线程加锁成功则释放锁 lock.unlock");
        //        long requestStartTime =
        //            (long)
        //                RPCWrapper.getHttpServletRequestRPCWrapper()
        //                    .getAttribute(ProviderRequtestParam.REQUESTSTARTTIME);

        //        long nowTime = System.currentTimeMillis();
        // 执行时间
        //        long exeTime = nowTime - requestStartTime;
        //        boolean moderThenConsumerReadTimeout = false;
        // provider无关代码执行时间以及真正到provider时consumer已经在计算请求处理时间了
        // 必须未超过请求处理时间才能释放锁考虑的是feign调用超时了在重试途中这里开始解锁导致重试的可以获取到锁再次执行upd,接口不幂等的情况会导致数据错乱
        //        超时的不主动释放锁在下个feign重试的时候获取不到锁直接抛出异常结束本次远程调用这样导致串行化远程调用,性能过低
        //        long comsumerReadTimeOut = providerRPCServiceImpl.getConsumerReadTimeout();
        //        if (lock.isLocked() && exeTime + ProviderRPCService.MORERPCTIMEOUT <
        // comsumerReadTimeOut) {

        if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
          lock.unlock();
          log.info(rpcId + "锁释放成功");
        } else {
          if (!lock.isLocked()) {
            log.info("redis锁已过期失效");
          }
          if (!lock.isHeldByCurrentThread()) {
            log.error("redis锁非当前线程持有");
          }
        }
//        锁释放处理完成再抛出异常
        if(exception!=null){
          throw exception;
        }
      }
      //        log.info("xid方法执行后测试finally是否最后执行先进后出xidaop:");
      //      }
    } else {
      result = invocation.proceed();
    }
    return result;

  }

  /**
   * @param table 表/类名
   * @param lockKey 锁名
   * @param invocation
   */
  public Object getRpcID(String table, String lockKey, MethodInvocation invocation) {
    LocalVariableTableParameterNameDiscoverer localVariableTableParameterNameDiscoverer =
        new LocalVariableTableParameterNameDiscoverer();
    String[] parameterNames =
        localVariableTableParameterNameDiscoverer.getParameterNames(invocation.getMethod());
    Object[] arguments = invocation.getArguments();
    for (int i = 0; i < arguments.length; i++) {
      Object arg = arguments[i];
      if (i == 0) {
        return arg;
      }
      log.info("argument: " + arg);
    }
    //    for(int i=0;i<parameters.length;i++){
    //
    //    }
    return null;
  }
//
//  public Object getTargetObjByCheckClass(Class<?> targetClass, Object needCheckObj)
//      throws IllegalAccessException {
//    if (targetClass == null || needCheckObj == null) {
//      return null;
//    }
//
//    Class needCheckClass = needCheckObj.getClass();
//    // 校验class类型是否相等
//    if (targetClass.equals(needCheckClass)) {
//      return needCheckObj;
//    }
//
//    while (needCheckClass.getSuperclass() != null) {
//      if (needCheckClass.getSuperclass().equals(targetClass)) {
//        return needCheckObj;
//      }
//    }
//    // 校验本对象包含父类所有字段是否与目标类型Class相同
//    Field[] fields = getAllFields(needCheckClass);
//    for (Field field : fields) {
//      if (field.getType().equals(targetClass)) {
//        if (!field.isAccessible()) {
//          field.setAccessible(true);
//        }
//        return field.get(needCheckObj);
//      }
//
//      System.out.println(
//          field.getType().getName() + "  " + field.getName() + "   " + field.get(needCheckObj));
//    }
//    return null;
//  }

  //  public static void main(String[] args) throws ClassNotFoundException {
  ////   System.out.println(
  ////
  // ClassUtils.isPresent("java.lang.annotation.Annotation",RpcidAnnotationnStrategy.class.getClassLoader()));
  ////    System.out.println(
  // Class.forName("java.lang.annotation.Annotation").equals(java.lang.annotation.Annotation.class));
  //    Object annotationStrategy= new RpcidAnnotationnStrategy();
  //    System.out.println(annotationStrategy.getClass().getName());
  //
  //  }

  private static Field[] getAllFields(Class<?> clazz) {
    List<Field> fieldList = new ArrayList<>();
    while (clazz != null) {
      fieldList.addAll(new ArrayList(Arrays.asList(clazz.getDeclaredFields())));
      clazz = clazz.getSuperclass();
    }
    Field[] fields = new Field[fieldList.size()];
    return fieldList.toArray(fields);
  }

  private static class Student extends RpcidAnnotationnStrategy {
    private String stuClass;
    private String stuNum;
    private String name;

    private String age;

    public String getStuClass() {
      return stuClass;
    }

    public void setStuClass(String stuClass) {
      this.stuClass = stuClass;
    }

    public String getStuNum() {
      return stuNum;
    }

    public void setStuNum(String stuNum) {
      this.stuNum = stuNum;
    }

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }

    public String getAge() {
      return age;
    }

    public void setAge(String age) {
      this.age = age;
    }
  }

  public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException {
    Student student = new Student();
    student.setStuClass("18001");
    student.setStuNum("19800101");
    student.setName("Tom");
    student.setAge("20");

    Class<? extends Student> clazz = student.getClass();
//    Object object = student.getTargetObjByCheckClass(Student.class, student);
//    if (object == student) {
//      System.out.println("get Fatcher");
//    }
//    Field[] fields = getAllFields(object.getClass());
//    for (Field field : fields) {
//      if (field.getName().equals("stuClass")) {
//        if (!field.isAccessible()) {
//          field.setAccessible(true);
//        }
//        System.out.println(
//            field.getType().getName() + "  " + field.getName() + "   " + field.get(student));
//      }
//    }
  }
}
